<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="root">
    <div class="c1">
      <div title="title1">hello1</div>
      <div title="title2">hello2</div>
      <div title="title3">hello3</div>
      <ul>
        <li>{{name}}</li>
        <li>{{age}}</li>
        <li>{{sex}}</li>
      </ul>
    </div>
  </div>

  <script>
    // 虚拟dom构造函数
    class VNode {
      constructor(tag,data,value,type) {
        this.tag = tag && tag.toLowerCase();// 文本的话tag为undefined
        this.data = data;
        this.value = value;
        this.type = type;
        this.children = [];
      }
      appendChild(vnode) {
        this.children.push(vnode)
      }
    }
    // 由html dom生成虚拟dom，将这个函数当做compiler函数（抽象语法树的函数）
    function getVNode(node) {
      let nodeType = node.nodeType; // 元素类型，1.文本 3.元素
      let _vnode = null;
      if(nodeType ===1) {
        // 元素
        let nodeName = node.nodeName;
        let attrs = node.attributes; // 伪数组
        let _attrObj = {};
        for(let i=0; i<attrs.length; i++) { // attrs[i] 属性节点，nodeType：2
          _attrObj[attrs[i].nodeName] = attrs[i].nodeValue;
        }
        _vnode = new VNode(nodeName, _attrObj, undefined, nodeType)

        // 考虑node真是dom的子元素
        let childNodes = node.childNodes;
        for(let i=0; i<childNodes.length; i++) {
          _vnode.appendChild(getVNode(childNodes[i])) // 递归
        }

      } else if(nodeType ===3) {
        // 文本
        _vnode = new VNode(undefined, undefined, node.nodeValue,nodeType)
      }
      return _vnode
    }
    // 将vnode转为真实dom
    function parseVNode(vnode) {

    }

    let rkuohao = /\{\{(.+?)\}\}/g;

    // 根据路径访问对象成员
    function getValueByPath(obj,path) {
    let paths = path.split('.');
    let res = obj;
    let prop;
    while(prop = paths.shift()) {
      res = res[prop]
    }
    return res
   }

    // render 将带有{{}}的vnode结合数据data生成 填充数据的vnode，模拟AST
    function combine(vnode, data) {
      let _type = vnode.type;
      let _data = vnode.data;
      let _value = vnode.value;
      let _tag = vnode.tag;
      let _children = vnode.children;

      let _vnode = null;
      if(_type === 3) {
        // 文本
        // 对文本处理
        _value = _value.replace(rkuohao, function(_,g) {
          return getValueByPath(data, g.trim())
        })
        _vnode = new VNode(_tag, _data, _value, _type)
      } else if(_type === 1) {
        // 元素
        _vnode = new VNode(_tag, _data, _value, _type);
        _children.forEach(_subvnode => _vnode.appendChild(combine(_subvnode, data)))
      }

      return _vnode;
    }

    function JGVue(options) {
      this._data = options.data;
      this.elm = document.querySelector(options.el);// vue中是字符串，这里简化了
      this._template = this.elm;
      this._parent = this.elm.parentNode;
      this.mount(); // 挂载
    }

    JGVue.prototype.mount = function() {
      // 提供一个render方法：生成虚拟dom（缓存功能）
      this.render = this.createRenderFn()
      // 为什么不把render挂载到vue原型上，因为render已经存在vue的原型上了，render返回虚拟dom节点，是一个createElement函数
      this.mountComponent()
    }

    JGVue.prototype.mountComponent = function() {
      // 执行mountComponent() 函数体如下
      let mount = () => { 
        this.update(this.render())
      }
      // 为什么用call，mount是一个函数，函数是全局作用域，函数里边调用update，和render，不是存在window下的，而是存在vue上的
      mount.call(this); // 本质上应该交给wacher调用，还没有到这个过程
      // 为什么不直接调用update，因为后期要使用发布订阅模式，渲染和计算的行为
    }

// 生成有render函数，目的是缓存抽象语法树(我们使用虚拟dom来模拟)
    JGVue.prototype.createRenderFn = function() {
      let ast = getVNode(this._template);
      // vue: 将AST+data => vnode,我们用{{}}的vnode+data =>含有数据的vnode
      return function render() {
        // 将{{}}的vnode转换为带数据的vnode
        let _tmp = combine(ast, this._data);
        return _tmp
      }
    }
// 将虚拟dom渲染到页面中，注意diff算法就在这里
    JGVue.prototype.update = function(vnode) {
      // 直接生成html dom replaceChild 到页面中
      // 父元素.replaceChild(新元素，旧元素)
      let realDom = parseVNode(vnode)
      this._parent.replaceChild(realDom, document.querySelector('#root'))
      // 不负责任的dom，
    }
/*
在vue中使用了二次提交的设计结构
在页面中的dom和虚拟dom是一一对应的关系
先由AST和数据生成vnode（新的数据，render）
将旧的vnode和新的vnode进行比较在进行更新（diff），更新（update）
*/

let app = new JGVue({
  el: '#root',
  data: {
    name: '张',
    age: 'dan',
    sex: '女',
  }
})
  </script>
</body>
</html>